package cn.suimg.neo4j.service.impl;


import cn.suimg.neo4j.bean.Album;
import cn.suimg.neo4j.bean.Singer;
import cn.suimg.neo4j.bean.Song;
import cn.suimg.neo4j.bean.Tag;
import cn.suimg.neo4j.graph.*;
import cn.suimg.neo4j.relationship.AttributeRelationship;
import cn.suimg.neo4j.relationship.IncludeRelationship;
import cn.suimg.neo4j.relationship.PublishRelationship;
import cn.suimg.neo4j.relationship.SingRelationship;
import cn.suimg.neo4j.repository.*;
import cn.suimg.neo4j.service.MusicNeoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.ogm.model.Result;
import org.neo4j.ogm.response.model.QueryResultModel;
import org.neo4j.ogm.session.Session;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.suimg.neo4j.config.Constant.*;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.*;
import static cn.suimg.neo4j.util.NeoGraphUtil.convert2NeoGraph;


@Slf4j
@Service
@AllArgsConstructor
public class MusicNeoServiceImpl implements MusicNeoService {

    private final Session session;

    private final SongGraphRepository songGraphRepository;

    private final SingerGraphRepository singerGraphRepository;

    private final SingRelationshipRepository singRelationshipRepository;

    private final AlbumGraphRepository albumGraphRepository;

    private final PublishRelationshipRepository publishRelationshipRepository;

    private final IncludeRelationshipRepository includeRelationshipRepository;

    private final TagGraphRepository tagGraphRepository;

    private final AttributeRelationshipRepository attributeRelationshipRepository;

    /**
     * log
     */
    @Override
    public boolean importSong(Song song) {
        //校验不为空
        Assert.notNull(song,"song can't be null");
        if (songGraphRepository.findBySongId(song.getId()) != null) return false;
        songGraphRepository.save(convert2NeoGraph(song));
        return true;
    }

    @Override
    public boolean importSinger(Singer singer) {
        //校验歌手不为空
        Assert.notNull(singer,"singer can't be null");
        if(singerGraphRepository.findBySingerId(singer.getId()) != null) return false;
        singerGraphRepository.save(convert2NeoGraph(singer));
        return true;
    }

    @Override
    public void importSingRelationship(Singer singer, List<Song> songList) {
        //校验歌手不为空
        Assert.notNull(singer,"singer can't be null");
        //校验歌曲列表不为空
        Assert.notNull(songList,"song list can't be null");
        //校验歌曲列表不为空列表
        Assert.notEmpty(songList,"song list can't be empty");
        SingerGraph singerGraph = singerGraphRepository.findBySingerId(singer.getId());
        //校验歌手已经在图库中
        Assert.notNull(singerGraph,"singer not in neo");
        songList.forEach(song -> {
            //校验歌曲不为空
            Assert.notNull(song,"song can't be null");
            SongGraph songGraph = songGraphRepository.findBySongId(song.getId());
            //校验歌曲已经在图库中
            Assert.notNull(songGraph,"song not in neo");
            SingRelationship singRelationship = singRelationshipRepository.findBySingerIdAndSongId(singer.getId(), song.getId());
            if(singRelationship == null){
                singRelationshipRepository.save(SingRelationship.builder()
                        .startNode(singerGraph)
                        .endNode(songGraph)
                        .build()
                );
            }else {
//                log.warn("singer:{} and song:{} already related",singer.getId(),song.getId());
            }
        });
    }

    @Override
    public boolean importAlbum(Album album) {
        //校验专辑不为空
        Assert.notNull(album,"album can't be null");
        if (albumGraphRepository.findByAlbumId(album.getId()) != null) return false;
        albumGraphRepository.save(convert2NeoGraph(album));
        return true;
    }

    @Override
    public void importPublishRelationship(Singer singer, List<Album> albumList) {
        //校验歌手不为空
        Assert.notNull(singer,"singer can't be null");
        //校验专辑列表不为空
        Assert.notNull(albumList,"album list can't be null");
        //校验专辑列表不为空列表
        Assert.notEmpty(albumList,"album list can't be empty");
        SingerGraph singerGraph = singerGraphRepository.findBySingerId(singer.getId());
        //校验歌手已经在图库中
        Assert.notNull(singerGraph,"singer not in neo");
        albumList.forEach(album -> {
            //校验专辑不为空
            Assert.notNull(album,"album can't be null");
            AlbumGraph albumGraph = albumGraphRepository.findByAlbumId(album.getId());
            //校验专辑已经在图库中
            Assert.notNull(albumGraph,"album not in neo");
            PublishRelationship publishRelationship = publishRelationshipRepository.findBySingerIdAndAlbumId(singer.getId(), album.getId());
            if(publishRelationship == null){
                publishRelationshipRepository.save(PublishRelationship.builder()
                        .startNode(singerGraph)
                        .endNode(albumGraph)
                        .time(album.getPublishTime().getTime())
                        .build()
                );
            }else {
//                log.warn("singer:{} and album:{} already related",singer.getId(),album.getId());
            }
        });
    }

    @Override
    public void importIncludeRelationship(Album album, List<Song> songList) {
        //校验专辑不为空
        Assert.notNull(album,"album can't be null");
        //校验歌曲列表不为空
        Assert.notNull(songList,"song list can't be null");
        //校验歌曲列表不为空列表
        Assert.notEmpty(songList,"song list can't be empty");
        AlbumGraph albumGraph = albumGraphRepository.findByAlbumId(album.getId());
        //校验专辑已经在图库中
        Assert.notNull(albumGraph,"album not in neo");
        songList.forEach(song -> {
            //校验歌曲不为空
            Assert.notNull(song,"song can't be null");
            SongGraph songGraph = songGraphRepository.findBySongId(song.getId());
            //校验歌曲已经在图库中
            Assert.notNull(songGraph,"song not in neo");
            IncludeRelationship includeRelationship = includeRelationshipRepository.findByAlbumIdAndSongId(album.getId(), song.getId());
            if(includeRelationship == null){
                includeRelationshipRepository.save(IncludeRelationship.builder()
                        .startNode(albumGraph)
                        .endNode(songGraph)
                        .build()
                );
            }else {
//                log.warn("album:{} and song:{} already related",album.getId(),song.getId());
            }
        });
    }


    @Override
    public void importTag(Tag tag) {
        //校验标签不为空
        Assert.notNull(tag,"tag can't be null");
        TagGraph tagGraph = tagGraphRepository.findByTagId(tag.getId());
        if(tagGraph != null)
            return;
        tagGraphRepository.save(convert2NeoGraph(tag));
    }

    @Override
    public void importAttributeRelationship(Tag tag, Song song) {
        //校验标签不为空
        Assert.notNull(tag,"tag can't be null");
        //校验不为空
        Assert.notNull(song,"song can't be null");

        TagGraph tagGraph = tagGraphRepository.findByTagId(tag.getId());
        Assert.notNull(tagGraph,"tag not in neo");
        SongGraph songGraph = songGraphRepository.findBySongId(song.getId());
        Assert.notNull(songGraph,"song not in neo");

        AttributeRelationship attributeRelationship = attributeRelationshipRepository.findByTagIdAndSongId(tag.getId(), song.getId());
        if(attributeRelationship == null){
            attributeRelationshipRepository.save(AttributeRelationship.builder().startNode(tagGraph).endNode(songGraph).build());
        }else {
//            log.warn("tag:{} and song:{} already related",tag.getId(),song.getId());
        }
    }

    @Override
    public Page<NeoGraph> searchSong(String singer, String album, String song, Integer pageNo, Integer pageSize,String type) {

        if(singer != null){
            singer = singer.trim();
        }
        if(album != null){
            album = album.trim();
        }
        if(song != null){
            song = song.trim();
        }
        String queryType;
        if(type == null) {
            return (Page<NeoGraph>) EMPTY_PAGE_RESULT;
        }
        switch (type){
            default:
            case "SONG":
                queryType = "songGraph";
                break;
            case "ALBUM":
                queryType = "albumGraph";
                break;
            case "SINGER":
                queryType = "singerGraph";
                break;
        }

        //分页需要跳过的数量
        int skip = (pageNo - 1) * pageSize;

        log.debug("singer:{},album:{},song:{},skip:{},limit:{},return:{}",singer,album,song,skip,pageSize,queryType);
        String[] cqlArray = getCQL(singer,album,song,skip,pageSize,queryType);
        if(cqlArray == null)
            return (Page<NeoGraph>) EMPTY_PAGE_RESULT;

        String countCql = cqlArray[0];
        String queryCql = cqlArray[1];
        log.debug("count cql:{}",countCql);
        log.debug("query cql:{}",queryCql);

        //先查询一下一共有多少条记录
        Result countResult = session.query(countCql, new HashMap<>());
        QueryResultModel countQueryResultModel = (QueryResultModel) countResult;
        long counts = (Long) countQueryResultModel.iterator().next().get("counts");


        //模糊搜索
        Result result = session.query(queryCql, new HashMap<>());
        QueryResultModel queryResultModel = (QueryResultModel) result;

//        queryResultModel.queryStatistics()

        List<NeoGraph> graphList = new ArrayList<>();
        queryResultModel.iterator().forEachRemaining(item -> graphList.add((NeoGraph) item.get(queryType)));
        return new PageImpl<>(graphList,Pageable.unpaged(),counts);
    }

    /**
     * 生成SQL 使用return 避免重复判断
     * @param singer
     * @param album
     * @param song
     * @param skip
     * @param limit
     * @param type
     * @return
     */
    private String[] getCQL(String singer,String album,String song,Integer skip,Integer limit,String type){
        //情况1.只有歌曲 返回歌曲
        if(isEmpty(singer) && isEmpty(album) && isNotEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_SONG,song),format(CQL_QUERY_WITH_SONG,song,skip,limit)};
        }

        //情况2 只有专辑 返回歌曲
        if(isEmpty(singer) && isNotEmpty(album) && isEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_ALBUM_RETURN_SONG,album),format(CQL_QUERY_WITH_ALBUM_RETURN_SONG,album,skip,limit)} ;
        }

        //情况3 只有歌手
        if(isNotEmpty(singer) && isEmpty(album) && isEmpty(song)){
            //返回专辑
            if("albumGraph".equals(type)){
                return new String[]{format(CQL_COUNT_WITH_SINGER_RETURN_ALBUM,singer),format(CQL_QUERY_WITH_SINGER_RETURN_ALBUM,singer,skip,limit)};
                //返回歌曲
            }else if("songGraph".equals(type)){
                return new String[]{format(CQL_COUNT_WITH_SINGER_RETURN_SONG,singer),format(CQL_QUERY_WITH_SINGER_RETURN_SONG,singer,skip,limit)};
            }
        }

        //情况4 只有专辑和歌曲
        if(isEmpty(singer) && isNotEmpty(album) && isNotEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_ALBUM_SONG,album,song,type),format(CQL_QUERY_WITH_ALBUM_SONG,album,song,type,skip,limit)};
        }

        //情况5 只有歌手和专辑
        if(isNotEmpty(singer) && isNotEmpty(album) && isEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_SINGER_ALBUM,singer,album,type),format(CQL_QUERY_WITH_SINGER_ALBUM,singer,album,type,skip,limit)};
        }

        //情况6 只有歌手和歌曲
        if(isNotEmpty(singer) && isEmpty(album) && isNotEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_SINGER_SONG,singer,song,type),format(CQL_QUERY_WITH_SINGER_SONG,singer,song,type,skip,limit)};
        }

        //情况7 只有歌手专辑和歌曲
        if(isNotEmpty(singer) && isNotEmpty(album) && isNotEmpty(song)){
            return new String[]{format(CQL_COUNT_WITH_SINGER_ALBUM_SONG,singer,album,song,type),format(CQL_QUERY_WITH_SINGER_ALBUM_SONG,singer,album,song,type,skip,limit)};
        }

        return null;
    }

    @Override
    public Page<AlbumGraph> getSingerAllAlbum(Integer singerId, Integer pageNo, Integer pageSize) {
        return albumGraphRepository.getBySingerId(singerId, PageRequest.of(pageNo, pageSize));
    }

    @Override
    public List<AlbumGraph> getSingerAllAlbum(Integer singerId) {
        return albumGraphRepository.findBySingerId(singerId);
    }

    @Override
    public Page<SongGraph> getSingerAllSong(Integer singerId, Integer pageNo, Integer pageSize) {
       return songGraphRepository.getBySingerId(singerId, PageRequest.of(pageNo, pageSize));
    }

    @Override
    public long getSingerCount() {
        return singerGraphRepository.count();
    }

    @Override
    public long getAlbumCount() {
        return albumGraphRepository.count();
    }

    @Override
    public long getSongCount() {
        return songGraphRepository.count();
    }


    @Override
    public long getAlbumCountBySingerId(Integer singerId) {
        return singerGraphRepository.countAlbumCount(singerId);
    }

    @Override
    public List<SingerGraph> getAllSinger(Integer pageNo,Integer pageSize) {
        return singerGraphRepository.findAll(PageRequest.of(pageNo,pageSize)).getContent();
    }

    @Override
    public List<AlbumGraph> getAllAlbum(Integer pageNo,Integer pageSize) {
        return albumGraphRepository.findAll(PageRequest.of(pageNo,pageSize)).getContent();
    }

    @Override
    public List<SongGraph> getAllSong(Integer pageNo,Integer pageSize) {
        return songGraphRepository.findAll(PageRequest.of(pageNo,pageSize)).getContent();
    }

    @Override
    public Page<SongGraph> getAlbumAllSong(Integer albumId, Integer pageNo, Integer pageSize) {
       return songGraphRepository.getByAlbumId(albumId, PageRequest.of(pageNo, pageSize));
    }

    @Override
    public List<SongGraph> getAlbumAllSong(Integer albumId) {
        return songGraphRepository.findByAlbumId(albumId);
    }

    @Override
    public Map<SingerGraph, List<SongGraph>> getSingedSingerAndSongBySingerId(Integer singerId) {
        Map<SingerGraph,List<SongGraph>> result = new HashMap<>();
        List<NeoGraph> graphList = singerGraphRepository.querySingedSingerAndSongBySingerId(singerId);
        graphList.forEach(graph0 -> {
            if(graph0 instanceof SingerGraph){
                SingerGraph singerGraph = (SingerGraph) graph0;
                List<SongGraph> itemList = new ArrayList<>();
                graphList.forEach(graph1 -> {
                    if(graph1 instanceof SongGraph){
                        SongGraph songGraph = (SongGraph) graph1;
                        if(singRelationshipRepository.checkSingRelationshipExistBySingerIdAndSongId(singerGraph.getSingerId(),songGraph.getSongId())){
                            itemList.add(songGraph);
                        }
                    }
                });
                result.put(singerGraph,itemList);
            }
        });
        return result;
    }

    @Override
    public List<SingerGraph> getSingedSingerBySongId(Integer songId) {
        return songGraphRepository.getSingedSingerBySongId(songId);
    }

    @Override
    public List<AlbumGraph> getIncludeSongAlbumList(Integer songId) {
        return songGraphRepository.getIncludeSongAlbumList(songId);
    }

    @Override
    public List<SingerGraph> getSingerListBySongId(Integer songId) {
        return singRelationshipRepository.querySingerListBySongId(songId);
    }
}
